<template>
  <div>
    <ElUpload
      ref="upFileImg"
      :action="action"
      :show-file-list="false"
      :headers="header"
      :disabled="isloading"
      :auto-upload="false"
      :on-success="upComplate"
      :before-upload="checkUpFile"
      :multiple="multiple"
      :accept="accept"
      :on-change="fileChange"
    >
      <slot v-if="files.length > 0" name="fileList" :files="files">
        <ul class="el-upload-list el-upload-list--picture-card">
          <li v-for="file in files" :key="file.FileId" class="el-upload-list__item is-success">
            <img
              :src="formatFileUri(file.FileUri)"
              :alt="file.FileName"
              class="el-upload-list__item-thumbnail"
            />

            <label class="el-upload-list__item-status-label">
              <i class="el-icon-upload-success el-icon-check"></i
            ></label>
            <span class="el-upload-list__item-actions">
              <span class="el-upload-list__item-delete">
                <Icon icon="ep:delete" @click.stop="dropFile(file.FileId)" />
              </span>
              <span v-if="allowAmplify" class="el-upload-list__item-delete">
                <Icon icon="ep:zoom-in" @click.stop="amplifyImg(file.FileId)" />
              </span>
              <span v-if="isOrderBy" class="el-upload-list__item-delete">
                <Icon icon="ep:top" @click.stop="setSort(file, 'up')" />
              </span>
              <span v-if="isOrderBy" class="el-upload-list__item-delete">
                <Icon icon="ep:bottom" @click.stop="setSort(file, 'bottom')" />
              </span>
            </span>
          </li>
        </ul>
      </slot>
      <slot v-if="upFilelimit != files.length" name="trigger">
        <Icon icon="ep:plus" class="uploader-icon" />
      </slot>
      <template #tip>
        <div v-if="isShowTip && upConfig != null">
          <slot
            name="upTip"
            :show="imgLimit ? imgLimit.Show : null"
            :UpShow="upConfig.UpShow"
            :error="error"
          >
            <div class="el-upload__tip">
              <span v-if="error" style="color: red">{{ error }}</span>
              <template v-else>
                <div style="height: 30px; line-height: 30px">{{ upConfig.UpShow }}</div>
                <div v-if="imgLimit && imgLimit.Show" style="height: 30px; line-height: 30px">{{
                  imgLimit.Show
                }}</div>
              </template>
            </div>
          </slot>
        </div>
      </template>
    </ElUpload>
    <Dialog title="图片预览" v-model="amplifyVisible">
      <img :src="imgSrc" alt="" />
    </Dialog>
  </div>
</template>

<script setup lang="ts">
import { ElUpload, UploadFile, UploadRawFile } from 'element-plus'
import * as fileApi from '@/api/base/file'
import sparkMD5 from 'spark-md5'
import { UpFileType } from '@/config/fileConfig'
import { loginStateStoreWithOut } from '@/store/modules/loginState'
import { propTypes } from '@/utils/propTypes'
import { ref, PropType, watch, unref } from 'vue'
import { fileUpConfig, UpFile, UpImgSet } from '@/api/types/file'
import { formatFileUri } from '@/utils'
import { Dialog } from '@/components/Dialog'
const props = defineProps({
  fileKey: propTypes.string,
  tag: propTypes.string,
  linkBizPk: propTypes.string,
  modelValue: propTypes.arrayOf(propTypes.string),
  multiple: propTypes.bool,
  upFilelimit: propTypes.number.def(1),
  allowAmplify: propTypes.bool.def(true),
  isOrderBy: propTypes.bool.def(false),
  isShowTip: propTypes.bool.def(true),
  source: propTypes.object,
  urlField: propTypes.string,
  imgSet: Object as PropType<UpImgSet>
})
const upFileImg = ref(null)
const emit = defineEmits(['change', 'update:modelValue', 'error', 'update:fileUri', 'load'])
const loginState = loginStateStoreWithOut()
const action = ref<string>('')
const error = ref<string | null>(null)
const isloading = ref<boolean>(false)
let isInit = false
const upConfig = ref<fileUpConfig | null>(null)
const files = ref<UpFile[]>([])
const amplifyVisible = ref<boolean>(false)
const accept = ref<string>('')
const imgSrc = ref<string>('')
const imgLimit = ref<UpImgSet | null>(null)
const header = ref<object>({
  accreditId: loginState.token
})
const changeEvent = (name) => {
  const fileId: string[] = []
  const src: string[] = []
  files.value.forEach((c) => {
    fileId.push(c.FileId)
    src.push(c.FileUri)
  })
  emit('update:modelValue', fileId)
  if (props.urlField != null && props.source != null) {
    unref(props.source)[props.urlField] = src
  }
  emit(name, fileId, files)
}
const dropFile = (fileId: string) => {
  isloading.value = true
  const index = unref(files).findIndex((c) => c.FileId == fileId)
  if (index != -1) {
    files.value.splice(index, 1)
  }
  isloading.value = false
  changeEvent('change')
}
const upComplate = (res) => {
  if (res.errorcode === 0) {
    addFile(res.data)
  }
}
const addFile = (file) => {
  files.value.push(file)
  changeEvent('change')
}
const amplifyImg = (fileId: string) => {
  const t = files.value.find((c) => c.FileId === fileId)
  imgSrc.value = formatFileUri(t.FileUri)
  amplifyVisible.value = true
}
const checkImgSize = (img) => {
  const limit = unref(imgLimit)
  if (limit == null) {
    return true
  } else if (limit.Ratio != null) {
    const sRatio = Math.round((limit.Ratio[0] / limit.Ratio[1]) * 100)
    const ratio = Math.round((img.width / img.height) * 100)
    if (sRatio !== ratio) {
      return false
    }
  }
  if (limit.MinWidth != null && img.width < limit.MinWidth) {
    return false
  }
  if (limit.MinHeight != null && img.height < limit.MinHeight) {
    return false
  }
  if (limit.MaxHeight != null && img.height > limit.MaxHeight) {
    return false
  }
  if (limit.MaxWidth != null && img.width > limit.MaxWidth) {
    return false
  }
  return true
}
const upFail = (err: string) => {
  error.value = err
  if (props.isShowTip === false) {
    emit('error', err)
  }
}
const checkImg = (file: UploadRawFile) => {
  return new Promise((resolve) => {
    if (imgLimit.value == null) {
      resolve(true)
      return
    }
    const reader = new FileReader()
    reader.onload = function (e) {
      if (e.target == null) {
        return
      }
      const img = new Image()
      img.onload = function () {
        if (!checkImgSize(img)) {
          const str = unref(imgLimit)?.Show
          upFail(str ?? '图片宽高不符合上传要求!')
          resolve(false)
        } else {
          resolve(true)
        }
      }
      img.src = e.target.result as string
    }
    reader.readAsDataURL(file)
  })
}

const loadMD5 = (file: Blob): Promise<string> => {
  const chunkSize = 2097152
  const chunks = Math.ceil(file.size / chunkSize)
  return new Promise((resolve, reject) => {
    let currentChunk = 0
    const spark = new sparkMD5.ArrayBuffer()
    const reader = new FileReader()
    reader.onload = function (e) {
      spark.append(e.target?.result)
      currentChunk++
      if (currentChunk < chunks) {
        loadNext()
      } else {
        resolve(spark.end())
      }
    }
    reader.onerror = function () {
      reject()
    }

    function loadNext() {
      const start = currentChunk * chunkSize
      const end = start + chunkSize >= file.size ? file.size : start + chunkSize
      reader.readAsArrayBuffer(file.slice(start, end))
    }
    loadNext()
  })
}
const setSort = (file: UpFile, type: string) => {
  const index = files.value.findIndex((c) => c.FileId === file.FileId)
  if (type === 'up') {
    if (index <= 0) {
      return
    }
    const list = files.value.concat()
    const other = list[index]
    list[index] = list[index - 1]
    list[index - 1] = other
    files.value = list
  } else {
    if (index >= files.value.length - 1) {
      return
    }
    const list = files.value.concat()
    const other = list[index]
    list[index] = list[index + 1]
    list[index + 1] = other
    files.value = list
  }
  saveSort()
}
const saveSort = async () => {
  const sort = {}
  for (let i = 0; i < files.value.length; i++) {
    sort[files[i].FileId] = i
  }
  await fileApi.saveSort(sort)
}
const reset = async () => {
  let uri = fileApi.getfileUpUri(props.fileKey)
  if (props.linkBizPk != null && props.linkBizPk !== '') {
    uri = uri + '?linkBizPk=' + props.linkBizPk + '&tag=' + (props.tag == null ? '' : props.tag)
  }
  action.value = uri
  const config = await fileApi.getUpConfig(props.fileKey, props.linkBizPk, props.tag)
  if (config.Extension == null) {
    config.Extension = []
  }
  if (config.UpImgSet !== undefined && imgLimit.value == null) {
    imgLimit.value = config.UpImgSet
  }
  accept.value = config.Extension.join(',')
  upConfig.value = config as fileUpConfig
  if (config.Files != null) {
    files.value = config.Files
  } else {
    files.value = []
  }
  changeEvent('load')
}
const getFileType = (type: string, name: string) => {
  const t = UpFileType[type]
  if (t) {
    return t
  }
  const index = name.lastIndexOf('.')
  if (index === -1) {
    return null
  }
  return name.substring(index)
}
const cancelUp = (file: UploadFile, fileList: UploadFile[]) => {
  if (upFileImg.value != null) {
    upFileImg.value.abort(file)
  }
  const index = fileList.findIndex((c) => c.uid === file.uid)
  fileList.splice(index, 1)
}
const preFileUp = async (md5: string, file: UploadFile, fileList: UploadFile[]) => {
  const res = await fileApi.preUpFile(
    {
      FileMd5: md5,
      FileName: file.name,
      LinkBizPk: props.linkBizPk,
      Tag: props.tag
    },
    props.fileKey
  )
  if (res.IsUp) {
    cancelUp(file, fileList)
    addFile(res)
  } else if (upFileImg.value != null) {
    upFileImg.value.submit()
  }
}
const checkUpFile = (file) => {
  const config = unref(upConfig)
  if (config == null) {
    return false
  } else if (config.Extension && config.Extension.length !== 0) {
    const type = getFileType(file.type, file.name)
    if (type == null) {
      error.value = '暂不支持该类型的文件!'
      return false
    } else if (!config.Extension.includes(type)) {
      error.value = '暂不支持' + type + '类型的文件!'
      return false
    }
  }
  if (config.UpFileSize && file.size > config.UpFileSize) {
    error.value =
      '上传文件大小不能超过 ' + Math.round((config.UpFileSize / 1024 / 1024) * 100) / 100 + 'MB'
    return false
  }
  return true
}
const fileChange = async (file: UploadFile, fileList: UploadFile[]) => {
  if (!checkUpFile(file)) {
    cancelUp(file, fileList)
    return
  } else if (file != null && file.raw != null) {
    const res = await checkImg(file.raw)
    if (res === false) {
      cancelUp(file, fileList)
    } else {
      const md5 = await loadMD5(file.raw)
      preFileUp(md5, file, fileList)
    }
  }
}
watch(
  () => props.fileKey,
  (val: String) => {
    if (val && isInit == false) {
      isInit = true
      reset()
    }
  },
  {
    immediate: true
  }
)
watch(
  () => props.linkBizPk,
  (val: String, old: string) => {
    if (val != old && props.fileKey) {
      reset()
    }
  },
  {
    immediate: false
  }
)

watch(
  () => props.imgSet,
  (val: UpImgSet | null) => {
    if (val == null && upConfig.value != null) {
      imgLimit.value = upConfig.value.UpImgSet ?? null
    } else {
      imgLimit.value = val
    }
  },
  {
    immediate: true
  }
)

watch(
  () => props.modelValue,
  (val: string[] | null) => {
    if (props.linkBizPk == null && (val == null || val.length === 0) && files.value.length !== 0) {
      files.value.splice(0, files.value.length)
    }
  },
  {
    immediate: false
  }
)
</script>

<style scoped>
.uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 120px;
  height: 120px;
  line-height: 120px;
  text-align: center;
  border: 1px;
  border-color: #8c939d;
  border-style: dashed;
}

.el-upload-list--picture-card {
  padding-left: 20px;
}

.uploader-img {
  height: 120px;
}
</style>
